package control;
import view.GameBoundary;
import view.Input;
import boundaryToMatador.GUI;
import model.*;

/**
 * The GameController stores variable representing all players, the current player, startbonus and dices,
 * it also offers methods to control the logic of our Matador game
 * @author Group 20
 */
public class GameController 
{
	// Fields
	private final int STARTBONUS = 4000;
	private Players players;
	private Player player;
	
	private DiceCup diceCup = new DiceCup();
	private GameBoundary gameBoundary = new GameBoundary();
	
	private int dice1Value, dice2Value, dicesSum;
	
	/**
	 * Initializes and runs a new game.
	 * @param autoAddPlayers: boolean value.
	 */
	public void start(boolean autoAddPlayers)
	{		
		addPlayers(autoAddPlayers);
		GameBoard.Initialize(players.getPlayers());
		initializeGUI();
			
		while(players.getNumOfPlayers() != 1)
		{
			if (!ContinueToPlay())
				break;
					
			getOutOfJail();
		
			if(!player.isInJail())
			{	
				movePlayer();
				Field field = GameBoard.getField(player.getStandOnField());
				drawCardIfNeeded(field);
				
				if(player.mayCarryOn())
				{
					field = GameBoard.getField(player.getStandOnField());
					payTaxIfTold(field);
					buyField(field);
					goToField(field);
					
					if(player != null)
					{
						gameBoundary.printPayment(field.getPayment(), player.getName()); 
						action();
					}
				}
			}
		}
		
		if (players.getNumOfPlayers() == 1)
		gameBoundary.printWinner(players.getWinner().getNumber()); // Get winner
	}
	
	
	/**
	 * Moves the player to a specific field.
	 * Makes sure, the player can pawn his fields or sell his houses, if field he lands on,
	 * requires more money from him, than he has.
	 * Updates the GUI afterwards.
	 * @param field: object of type Field.
	 */
	private void goToField(Field field)
	{
		if(!field.landOnField(player, dicesSum, GameBoard.numFieldType(field)))
		{
			while(player.getBalance() < (-field.getPayment()))
			{
				String action = gameBoundary.selectAction(player.getName(), GameBoard.hasPawnableFields(player), GameBoard.hasSellableBuildings(player));
				
				if(action.equals("I'm out!"))
				{
					removePlayerFromGame();
					break;
				}
				
				if (action.equals("Sell house"))
					sellHouse();

				if (action.equals("Pawn field"))
					pawnField();
			}
			
			if (player != null)
				field.landOnField(player, dicesSum, GameBoard.numFieldType(field));
		}
		
		updateGUI();
	}
	
	/**
	 * Removes a player from the game. Set his balance om the GUI to 0 and removes his car.
	 * Remove him as owner of all his fields.
	 * Updates the GUI afterwards.
	 */
	private void removePlayerFromGame()
	{
		player.setCarryOn(false);
		GUI.setBalance(player.getColor(), 0);
		GUI.removeCar(player.getColor());
		GameBoard.clearOwnedFields(player);
		players.removePlayer(player);
		player = null;
		updateGUI();
	}
	
	/**
	 * Draws a card.
	 * Asks the player to pawn fields or sell houses, if he is not able to pay.
	 * Removes player, if he get bankrupt.
	 * Updates GUI at the end.
	 * @param card: object of type Card.
	 */
	private void pickCard(Card card)
	{
		if(!card.draw(player))
		{
			while(player.getBalance() < (-card.getPayment()))
			{
				String action = gameBoundary.selectAction(player.getName(), GameBoard.hasPawnableFields(player), GameBoard.hasSellableBuildings(player));
				
				if(action.equals("I'm out!"))
				{
					removePlayerFromGame();
					break;
				}
				
				if (action.equals("Sell house"))
					sellHouse();

				if (action.equals("Pawn field"))
					pawnField();
			
			}
			if (player != null)
				card.draw(player);
		}
		updateGUI();
		
	}
	
	/**
	 * Let the current player choose between to taxes to pay.
	 * @param field: object of type Field.
	 */
	private void payTaxIfTold(Field field)
	{
		if (field instanceof TaxSelect)
		{
			TaxSelect taxSelect = ((TaxSelect)field);
			String taxSelection = gameBoundary.selectTax(player.getBalance());
			
			if (taxSelection.equals("4000,-"))
				taxSelect.setTax(4000);

			else
				taxSelect.setTax((player.getBalance() + GameBoard.getValueAllAssets(player))/10);
		}		
	}
	
	/**
	 * Asks the current player, what he will do in his turn.
	 * Only options relevant to the player is showed on the menu.
	 */
	private void action()
	{
		String action = "";

		while(!action.equals("End turn"))
		{
			if( (!GameBoard.hasPawnableFields(player) && !GameBoard.hasBuildableFields(player) && !GameBoard.hasFieldsWithSellableBuilding(player) && !GameBoard.hasUnPawnableFields(player)))
				break;
			
			action = gameBoundary.selectAction(player.getName(), GameBoard.hasPawnableFields(player), GameBoard.hasBuildableFields(player), GameBoard.hasFieldsWithSellableBuilding(player),GameBoard.hasUnPawnableFields(player));

			if (action.equals("Buy house"))
				buyHouse();

			if (action.equals("Sell house"))
				sellHouse();

			if (action.equals("Pawn field"))
				pawnField();
			
			if (action.equals("Unpawn field"))
				unPawnField();
		}
	}
	
	/**
	 * Picks a chance card for the current player, if the player is landed on a chance field.
	 * Updates GUI at the end.
	 * @param field: object of type Field.
	 */
	private void drawCardIfNeeded(Field field)
	{
		
		if (field instanceof Chance)
		{
			GameBoard.updateDependencyCards(player);
			Card drawnCard = GameBoard.getCard(players.getPlayers());
			updateGUI();
					
			gameBoundary.printFieldName(field.getName(), player.getName());
		
			pickCard(drawnCard);
		}
		
		else
			gameBoundary.printFieldName(field.getName(), player.getName());	
		
		updateGUI();
	}
	
	/**
	 * Determines which player should have the next turn.
	 * Rolls the dice for the next player. Two of a kind gives extra turn,
	 * unless you do it 3 times, then you go to jail.
	 * @return boolean value.
	 */
	private boolean ContinueToPlay()
	{
		if (player == null)
		{
			player = players.getNextActivePlayer();
			player.setCarryOn(true);
		}
		
		else if(player.getNumOfDicePairs() == 0)
		{
			player = players.getNextActivePlayer();
			player.setCarryOn(true);
		}
		
		if(player.isInJail())
			Input.showMessage("Player " + player.getNumber() + ". You're in jail. Roll two of a kind or use a 'Get out of jail' card to free yourself!");

		int selection = gameBoundary.selection(player.getNumber(), player.getName());
									
		if (selection == 1)
		{
			GUI.close();
			return false;
		}

		playerRoll();
		
		if(diceCup.getEns())
		{
			player.setNumOfDicePairs(player.getNumOfDicePairs()+1);
			
			if(player.getNumOfDicePairs() == 3)
			{
				player.setInJail(true);
				player.setStandOnField(11);
				player.setNumOfDicePairs(0);
				updateGUI();
				Input.showMessage("You rolled two of a kind three times in a row, you're put in Jail.");
			}
			
			else if(diceCup.getEns() && !player.isInJail())
				Input.showMessage("You rolled two of a kind you get an extra turn, unless you reach jail!");
		}
		
		else
			player.setNumOfDicePairs(0);
		
		return true;
	}
	
	/**
	 * Set the player out of jail, if he rolls two of a kind or uses an "get out of jail" card.
	 */
	private void getOutOfJail()
	{
		if(player.getNumOfDicePairs() > 0)
		{
			if(player.isInJail())
			{
				if(diceCup.getEns())
				{
					player.setInJail(false);
					player.setNumOfDicePairs(0);
					Input.showMessage("You rolled two of a kind. You get out of jail!");
				}
							
				else if(player.hasOutOfJailCard())
				{
					Input.showMessage("You have used a get out of jail card!");
					player.setInJail(false);
					player.useJailCard(true);
					player.setNumOfDicePairs(0);
				}
			}
		}
		
		if(player.isInJail())
			Input.showMessage("Boo hoo. You're still in jail!");
	}
	
	/**
	 * Add players to GUI automatically or manually.
	 * @param autoAddPlayers: boolean value.
	 */
	private void addPlayers(boolean autoAddPlayers)
	{
		if (autoAddPlayers)
		{
			players = new Players(3);
			players.addPlayer("Benjamin", 4, 1);
			players.addPlayer("Mike", 1, 2);
			players.addPlayer("Martine", 5, 3);
		}
		else 
		{
			int numOfPlayers = gameBoundary.getNumOfPlayers();
			players = new Players(numOfPlayers);

			for (int i = 1; i <= numOfPlayers; i++) 
				players.addPlayer(gameBoundary.getName(i), gameBoundary.getColor(), i);
		}
		
		player = players.getLastPlayer();
	}
	
	/**
	 * Buy a house for the current player.
	 * Updates GUI.
	 */
	private void buyHouse()
	{
		CustomArray<Field> buildableFields = GameBoard.getBuildableFields(player);
		String chosenBuildableFieldName = gameBoundary.selectFieldToBuildOn(translateFieldToString("Buy house", buildableFields), player.getName());
		
		
		for(Field buildableField : buildableFields)
		{
			if (buildableField.getName().equals(chosenBuildableFieldName))
			{
				Street street = (Street)buildableField;
				street.buyHouse();			
				player.withdraw(street.getHousePrice());
				updateGUI();
			}
		}
	}
	
	/**
	 * Sell a house for the current player.
	 * Updates GUI.
	 */	
	private void sellHouse()
	{
		CustomArray<Field> fieldWithSellableHouses = GameBoard.getFieldsWithSellableBuilding(player);
		String choice = gameBoundary.selectFieldToBuildOn(translateFieldToString("Sell house", fieldWithSellableHouses), player.getName());
		for(Field field : fieldWithSellableHouses)
		{
			if(field.getName().equals(choice))
			{
				Street street = (Street)field;
				street.sellHouse();
				player.deposit(street.getHousePrice()/2);
				updateGUI();
			}
		}
	}

	/**
	 * Pawn a field for the current player.
	 * Updates GUI.
	 */
	private void pawnField()
	{
		CustomArray<Field> pawnableFields = GameBoard.getPawnableFields(player);
		String choice = gameBoundary.selectFieldToBuildOn(translateFieldToString("Pawn field", pawnableFields), player.getName());
		for(Field field : pawnableFields)
		{
			if(field.getName().equals(choice))
			{
				Ownable ownableField = (Ownable)field;
				ownableField.pawn();
				player.deposit(ownableField.getPrice()/2);
				updateGUI();	
			}
		}
	}
	
	/**
	 * Unpawn a field for the current player.
	 * Updates GUI.
	 */
	private void unPawnField()
	{
		CustomArray<Field> unPawnableFields = GameBoard.getUnPawnableFields(player);
		String choice = gameBoundary.selectFieldToBuildOn(translateFieldToString("Unpawn field", unPawnableFields), player.getName());
		
		for(Field field : unPawnableFields)
		{
			if(field.getName().equals(choice))
			{
				Ownable ownableField = (Ownable)field;
				ownableField.unpawn();
				player.withdraw((ownableField.getPrice()/2)+((ownableField.getPrice()/2)/10));
				updateGUI();	
			}
		}
	}
	
	/**
	 * Moves the current player clockwise on the board depending on his dices.
	 * The player recieves 4000 if he passes "Start".
	 * Updates GUI.
	 */
	private void movePlayer()
	{
		player.setStandOnField(player.getStandOnField() + dicesSum);
		
		if (player.getStandOnField() > 40)
		{
			player.setStandOnField(player.getStandOnField() - 40);
			player.deposit(STARTBONUS);
		}		
		
		updateGUI();
	}
	
	/**
	 * Turn a field in a CustomArray into a String with field name and price/income on selling/pawning.
	 * @param fieldType: String describing field type.
	 * @param fields: Array of Fields.
	 * @return Array of strings.
	 */
	private String[] translateFieldToString(String fieldType, CustomArray<Field> fields)
	{
		String[] fieldNames = new String[fields.size()];

		if (fieldType.equals("Buy field"))
		{
			for (int i = 0; i < fields.size(); i++) 
			{
				fieldNames[i] = ((Field)fields.get(i)).getName() + " (" + ((Ownable)fields.get(i)).getPrice() + "kr.)"; 
			}
		}
		
		if (fieldType.equals("Pawn field"))
		{
			for (int i = 0; i < fields.size(); i++) 
			{
				fieldNames[i] = ((Field)fields.get(i)).getName() + " (" + ((Ownable)fields.get(i)).getPrice()/2 + "kr.)"; 
			}
		}
		
		if (fieldType.equals("Unpawn field"))
		{
			for (int i = 0; i < fields.size(); i++) 
			{
				fieldNames[i] = ((Field)fields.get(i)).getName() + " (" + (((Ownable)fields.get(i)).getPrice()/2)*1.1 + "kr.)"; 
			}
		}
		
		if (fieldType.equals("Buy house"))
		{
			for (int i = 0; i < fields.size(); i++) 
			{
				fieldNames[i] = ((Field)fields.get(i)).getName() + " (" + ((Street)fields.get(i)).getHousePrice() + "kr.)"; 
			}
		}
		
		if (fieldType.equals("Sell house"))
		{
			for (int i = 0; i < fields.size(); i++) 
			{
				fieldNames[i] = ((Field)fields.get(i)).getName() + " (" + ((Street)fields.get(i)).getHousePrice()/2 + "kr.)"; 
			}
		}
		
		return fieldNames;
	}
	
	
	/**
	 * Make GUI ready for a new game by adding the correct texts to the fields
	 * and placing the players and their cars
	 */
	private void initializeGUI()
	{
		for (Field field : GameBoard.getAllFields())
		{
			GUI.setSubText(field.getNumber(), field.getDescription());
			
			if(!(field instanceof Misc) && !(field instanceof Chance))
				GUI.setTitleText(field.getNumber(), field.getName());			
		}
		
		for (Player player : players.getPlayers()) 
		{
			GUI.addPlayer(player.getName(), player.getBalance(), player.getColor());
			GUI.setCar(1, player.getColor());
		}
	}
	
	/**
	 * Rolls dices for the current player
	 */
	private void playerRoll()
	{
		diceCup.rollDices();
		this.dicesSum = diceCup.getSum();
		this.dice1Value = diceCup.Dice1Value();
		this.dice2Value = diceCup.Dice2Value();
		updateGUI();
	}
	
	/**
	 * Updates field texts, ownerships, balances and cars in the GUI
	 */
	private void updateGUI()
	{
		GUI.setDice(dice1Value, dice2Value);
		GUI.setNextChanceCardText(GameBoard.getCurrentCardDescription());
		
		for (Player player : players.getPlayers())
		{
			if (player != null)
			{
				GUI.setBalance(player.getColor(), player.getBalance());
				GUI.setCar(player.getStandOnField(), player.getColor());
			}
		}
		
		for (Field field : GameBoard.getAllFields())
		{
			if (field instanceof Ownable)
			{
				Ownable ownableField=((Ownable)field);
				
				if (ownableField.isOwned())
				{
					GUI.setOwner(ownableField.getNumber(), ownableField.getOwner().getColor());
			
					if (ownableField.isPawned())
						GUI.setSubText(ownableField.getNumber(), "Pawned");
				}
				
				else
				{
					GUI.removeOwner(field.getNumber());
					GUI.setSubText(field.getNumber(), field.getDescription());
				}	
			}
			
			if (field instanceof Street)
			{
				Street street=((Street)field);
				GUI.setHouses(street.getNumber(), street.getNumOfHouses());
				
				if (street.hasHotel())
					GUI.setHotel(street.getNumber(), street.hasHotel());
			}
		}
		if (player != null)
			GUI.setCar(player.getStandOnField(), player.getColor());
	}
	
	
	/**
	 * Let the current player buy a field.
	 * Updates GUI.
	 * @param field: object of type Field.
	 */
	public void buyField(Field field)
	{
		if (field instanceof Ownable)
		{
			Ownable ownableField = (Ownable) field;
			
			if (!ownableField.isOwned())
			{
				if (player.getBalance() >= ownableField.getPrice())
				{
					if (gameBoundary.wantToBuy(ownableField.getPrice(), field.getName(),player.getName()))
					{
						ownableField.buy(player); 
						
						updateGUI();
					}
				}
			}
		}
	}
}